//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#ifndef _ATA_RW_H_
#define _ATA_RW_H_

INLINE void ResetController(ioport_t iobase)
{
    OutbDelay(IDEPORT_DEVICE_CONTROL(iobase), DCR_(DCR_SRST | DCR_nIEN));

    Outb(IDEPORT_DEVICE_CONTROL(iobase), DCR_(DCR_nIEN));
}

INLINE void MaskControllerInterrupt(ioport_t iobase, bool_t bEnable)
{
    if (bEnable)
        Outb(IDEPORT_DEVICE_CONTROL(iobase), DCR_(0));
    else
        Outb(IDEPORT_DEVICE_CONTROL(iobase), DCR_(DCR_nIEN));
}

INLINE uint8_t WaitForController(ioport_t iobase)
{
    while (TRUE) {
//        uint8_t u8Status = Inb(IDEPORT_STATUS(iobase));
//        kprintf("Error");
        uint8_t u8Status = Inb(IDEPORT_ALTERNATE_STATUS(iobase));
        if (!(u8Status & SR_BSY)) return u8Status;
//        DzDelay(1);
    }
}

INLINE uint8_t ExecuteDriveDiagnostics(ioport_t iobase)
{
    WaitForController(iobase);

    Outb(IDEPORT_COMMAND(iobase), IDECommand_ExecuteDriveDiagnostics);

    WaitForController(iobase);

    return Inb(IDEPORT_ERROR(iobase));
}

INLINE void InitializeDeviceParameters(
    ioport_t iobase, uint_t uDriveNo, uint16_t u16NumberOfLogicalHeads,
    uint16_t u16NumberOfLogicalSectorsPerTrack)
{
    assert(0 == uDriveNo || 1 == uDriveNo);

    uint8_t u8Drive = (0 == uDriveNo)? 0 : DHR_DEV;

    WaitForController(iobase);

    Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(u8Drive | u16NumberOfLogicalHeads));

    Outb(IDEPORT_SECTOR_COUNT(iobase), u16NumberOfLogicalSectorsPerTrack);

    Outb(IDEPORT_COMMAND(iobase), IDECommand_InitializeDeviceParameters);

    WaitForController(iobase);
}

INLINE ECode IdentifyDevice(
    ioport_t iobase, uint_t uDriveNo, IDEDeviceInfo *pDeviceInfo)
{
    assert(0 == uDriveNo || 1 == uDriveNo);

    if (0 == uDriveNo)
        Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(0));
    else
        Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(DHR_DEV));

    WaitForController(iobase);

    Outb(IDEPORT_COMMAND(iobase), IDECommand_IdentifyDevice);

    uint8_t u8Status = WaitForController(iobase);

    for (uint_t i = 0; !(u8Status & SR_DRQ) && !(u8Status & SR_ERR); i++) {
        u8Status = Inb(IDEPORT_STATUS(iobase));

        // TODO: Sure?
        if (100 <= i) {
//            kprintf("*ERROR* Identify device %d timeout (0x%x:0x%x)\n",
//                    uDriveNo, u8Status, Inb(IDEPORT_ERROR(iobase)));
//            BspBeep();
            return E_FAIL;
        }
    }

//    kprintf("The Status is: %x\n", u8Status);
    if (u8Status & (SR_ERR | SR_DF)) {
//        kprintf("*ERROR* Failed to identify device %d (0x%x:0x%x)\n",
//                uDriveNo, u8Status, Inb(IDEPORT_ERROR(iobase)));
//        BspBeep();
        return E_FAIL;
    }

    Insw(IDEPORT_DATA(iobase), pDeviceInfo, sizeof(IDEDeviceInfo));

    return NOERROR;
}

INLINE ECode ReadSectors(
    ioport_t iobase, AddressingMode mode,
    uint_t uDriveNo, uint16_t u16CylinderNo,
    uint8_t u8HeadNo, uint8_t u8SectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToRead, uint_t *puNumberOfSectorsRead,
    DzEvent * pEvent, int * pnIDECommand)
{
    ECode ec = NOERROR;
    uint_t uSign;
    uint_t i;
    uint8_t u8Status;

    assert(0 == uDriveNo || 1 == uDriveNo);

    uint8_t u8Drive = (0 == uDriveNo)? 0 : DHR_DEV;

    WaitForController(iobase);

    Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(mode | u8Drive | u8HeadNo));

    Outb(IDEPORT_SECTOR_COUNT(iobase), (uint8_t)uNumberOfSectorsToRead);

    Outb(IDEPORT_SECTOR_NUMBER(iobase), u8SectorNo);

    Outb(IDEPORT_CYLINDER_LOW(iobase), LOBYTE(u16CylinderNo));

    Outb(IDEPORT_CYLINDER_HIGH(iobase), HIBYTE(u16CylinderNo));

    *pnIDECommand = IDECommand_ReadSectors;

    Outb(IDEPORT_COMMAND(iobase), IDECommand_ReadSectors);

    WaitResult wr;
    for (i = 0; i < uNumberOfSectorsToRead; i++) {
        pEvent->TryWait(1000, &wr, (EventState *)&uSign);
        pEvent->Clear();

        u8Status = (uint8_t)uSign;

        if (WaitResult_TimedOut == wr) {
            u8Status = WaitForController(iobase);
            kprintf("*ERROR* Read sectors timeout %d (0x%x:0x%x)\n",
                    uDriveNo, u8Status, Inb(IDEPORT_ERROR(iobase)));
//            BspBeep();
//            ec = E_FAIL;
//            goto Exit;
        }

        if (u8Status & SR_ERR) {
            kprintf("*ERROR* Failed to read sectors %d (0x%x:0x%x)\n",
                    uDriveNo, u8Status, Inb(IDEPORT_ERROR(iobase)));
            BspBeep();
            ec = E_FAIL;
            goto Exit;
        }

        if (u8Status & SR_CORR) ec = S_FALSE;

        Insw(IDEPORT_DATA(iobase), pu8Buffer, SECTOR_SIZE);

        pu8Buffer += SECTOR_SIZE;
    }

Exit:
    *puNumberOfSectorsRead = i;
    *pnIDECommand = IDECommand_NOP;
    return ec;
}

INLINE ECode WriteSectors(
    ioport_t iobase, AddressingMode mode,
    uint_t uDriveNo, uint16_t u16CylinderNo,
    uint8_t u8HeadNo, uint8_t u8SectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToWrite, uint_t *puNumberOfSectorsWritten,
    DzEvent * pEvent, int * pnIDECommand)
{
    assert(0 == uDriveNo || 1 == uDriveNo);

    uint8_t u8Drive = (0 == uDriveNo)? 0 : DHR_DEV;

    WaitForController(iobase);

    Outb(IDEPORT_DRIVE_HEAD(iobase), DHR_(mode | u8Drive | u8HeadNo));

    Outb(IDEPORT_SECTOR_COUNT(iobase), (uint8_t)uNumberOfSectorsToWrite);

    Outb(IDEPORT_SECTOR_NUMBER(iobase), u8SectorNo);

    Outb(IDEPORT_CYLINDER_LOW(iobase), LOBYTE(u16CylinderNo));

    Outb(IDEPORT_CYLINDER_HIGH(iobase), HIBYTE(u16CylinderNo));

    *pnIDECommand = IDECommand_WriteSectors;

    Outb(IDEPORT_COMMAND(iobase), IDECommand_WriteSectors);

    ECode ec = NOERROR;
    uint_t uSign;
    uint_t i;
    uint8_t u8Status;

    u8Status = WaitForController(iobase);
    WaitResult wr;
    for (i = 0; i < uNumberOfSectorsToWrite; i++) {
        Outsw(IDEPORT_DATA(iobase), pu8Buffer, SECTOR_SIZE);

        pEvent->TryWait(2000, &wr, (EventState *)&uSign);
        pEvent->Clear();

        pu8Buffer += SECTOR_SIZE;

        u8Status = (uint8_t)uSign;

        if (WaitResult_TimedOut == wr) {
            u8Status = WaitForController(iobase);
            kprintf("*ERROR* Write sectors timeout %d (0x%x:0x%x)\n",
                    uDriveNo, u8Status, Inb(IDEPORT_ERROR(iobase)));
//            BspBeep();
//            ec = E_FAIL;
//            goto Exit;
        }

        if (u8Status & SR_ERR) {
            kprintf("*ERROR* Failed to write sectors %d (0x%x:0x%x)\n",
                    uDriveNo, u8Status, Inb(IDEPORT_ERROR(iobase)));
            BspBeep();
            ec = E_FAIL;
            goto Exit;
        }

        if (u8Status & SR_CORR) ec = S_FALSE;
    }

Exit:
    *pnIDECommand = IDECommand_NOP;
    *puNumberOfSectorsWritten = i;
    return ec;
}

#endif  //_ATA_RW_H_
